那些年不懂的JS - Scope 範疇


Posted by Andy Tsai on 2020-04-19

Scope是什麼?

Scope就是變數的有效範圍(可以被取用的範圍),一旦超出範圍就無法存取,
可以分為幾種-全域範疇、函式範疇、區塊範疇

Global Scope 全域範疇:

全域範疇指的是最外層的範疇,精準來說是瀏覽器中BOM的window物件,

想了解更多瀏覽器知識,可以看看kuro大大的文章
重新認識 JavaScript: Day 11 前端工程師的主戰場:瀏覽器裡的 JavaScript

我們在全域範疇設的變數(全域變數),都會成為全域物件的屬性
所以我們也可以用 window. 去取得我們得變數

var a = 1; // 全域變數
console.log(window.a) // 1

全域範疇的有效範圍是全部,在代碼中的任何地方都可以取用。

Function Scope 函式範疇

每個函式都會有自己的範疇,有效範圍只在函式內部,外層無法直接訪問函式中的範疇,
並且函式結束後,裡面的變數就無法取得了。

function foo() {
    var a =3
    function test(){
        console.log(a) // 3 在有效範圍中,順利取得
    }
    console.log(a) // 3 在有效範圍中,順利取得
    test()
}
foo()
console.log(a) // a is not defined 超出範圍,取不到function內的變數

另外要注意的是,如果沒有用var宣告,就算在函式內也會被視為全域變數,
盡量不要這樣做,因為污染全域不是一個好主意。

Scope Chain 範圍鏈

插撥一個觀念,大家會發現上面的代碼中foo()裡面的test(),也取得到外層的a變數,
這其實就是Scope Chain(範圍鏈)的概念,當它在範疇內找不到變數時,會從自己的層級開始,一層層往上找,直到找到第一個符合的為止。

Block Scope 區塊範疇

在ES6以前,並沒有區塊範疇的概念,我們只能以函式為區分,
而從ES6開始,引入了Let、Const關鍵字,它們以區塊{}當範疇,如此一來我們就可以利用區塊範疇寫出更嚴謹,更好維護的代碼。

{
  var a = 1;
  let b = 10;
}
console.log(a); // 1
console.log(b); // b is not defined

可以看到上面代碼中,外層的console.log(b)是取不到的,因為b用let宣告,所以它只在{}中有效,超出範圍當然就取不到了

區塊範疇的好處不少,它能讓我們更靈活地管理變數,更簡潔明確的邏輯劃分,並把資訊的隱藏從函式層級進階到區塊層級,也更符合最小權限原則

舉個例子,這是一個看似平凡的for迴圈

for(var i=0; i<10; i++{
    console.log(i)
}

但這裡的var i是全域變數,可是我們只會在for迴圈裡中使用這個i,那麼為何要污染全域範疇呢?
而且還會造成潛在的問題,如果i在其他地方被誤用? 如果某處又宣告了i造成命名衝突?

所以更好的做法是把它改成let,鎖在{}中。

Let與迴圈

如果我們在for迴圈使用let宣告,它除了會將變數鎖在for的{}之外,還會重新繫結到迴圈的每次迭代,確保每次的變數都是前一次迭代的結果。

來看一個面試中常見的題目,以下代碼的執行結果為?

for(var i = 0; i < 5; i++) {
  setTimeout(function() {
     console.log(i); 
  }, 0);
};

很多新手會答01234(我以前也是),但這是錯誤的,正解是 55555

原因是,var會讓i成為一個全域變數,而setTimeout會在for跑完之後才執行,所以當setTimeout實際執行時,這時i已經是5了

可能有人會問,為什麼setTimeout在for跑完之後才執行?
這牽扯到Event Queue的觀念,如果不了解,可以看我上一篇文章
理解Javascript中的Event Loop

要讓它正確顯示01234,很簡單,用let來宣告i就可以了,同時我們也知道它有重新繫結的特性,所以可以確保每次迭代中的i都會是前一次迭代的結果。

Reference:
You Don't Know JS: Scope & Closures


#javascript #scope #範疇 #w3HexSchool







Related Posts

OOP - 7 關於封裝

OOP - 7 關於封裝

React 用 key 強迫 component 重新渲染

React 用 key 強迫 component 重新渲染

MTR04_1022

MTR04_1022


Comments